1 Summary

This R Notebook generates figures for idiopathic pulmonary fibrosis (IPF) lung dataset in Real Data Analysis section in manuscript.

  1. Inputs:
  • IPF_spatial_data.rds: A Seurat object of IPF dataset, including raw nUMI and physical locations of spatial spots, and a thumbnail of tissue image. In total 4,992 spatial spots and 60,651 genes are included.
  • IPF_all_results.rds: cell type deconvolution results of all methods in IPF data analysis.
  1. Outputs:
  • Figure 6B. Heatmap of 4 cell type-specific marker genes
  • Figure 6C. Visualization of inferred cell type proportions in each spot of 4 methods
  • Figure 6D. Barplot of weighted mean of expressions of 4 cell type-specific marker genes
  • Figure 6E. Heatmap of pairwise correlation of estimated cell type proportions
  • Figure S12. Visualization of inferred cell type proportions in each spot of all methods

2 Version

version[['version.string']]
[1] "R version 4.2.2 Patched (2022-11-10 r83330)"
print(sprintf('Package %s version: %s', 'ggplot2', packageVersion('ggplot2')))
[1] "Package ggplot2 version: 3.4.2"
print(sprintf('Package %s version: %s', 'ComplexHeatmap', packageVersion('ComplexHeatmap')))
[1] "Package ComplexHeatmap version: 2.14.0"
print(sprintf('Package %s version: %s', 'ggpubr', packageVersion('ggpubr')))
[1] "Package ggpubr version: 0.6.0"
print(sprintf('Package %s version: %s', 'multipanelfigure', packageVersion('multipanelfigure')))
[1] "Package multipanelfigure version: 2.1.2"
print(sprintf('Package %s version: %s', 'Seurat', packageVersion('Seurat')))
[1] "Package Seurat version: 4.3.0.1"

3 Read relevant files

3.1 Read Seurat object of spatial data

file_name = file.path(home.dir, 'IPF_spatial_data.rds')
ipf_obj = readRDS(file_name)
print(sprintf('load data from %s', file_name))
[1] "load data from /home/hill103/Documents/Spatial/Figures/IPF/IPF_spatial_data.rds"
print(sprintf('genes: %d, spots: %d', ncol(ipf_obj), nrow(ipf_obj)))
[1] "genes: 4992, spots: 60651"

only focus on spots covered by tissue

# add tissue indicator into meta data
total_spots = ncol(ipf_obj)
tmp.df = ipf_obj@images[[1]]@coordinates
tmp.df = tmp.df[colnames(ipf_obj), ]
stopifnot(nrow(tmp.df) == ncol(ipf_obj))
ipf_obj[['tissue']] = tmp.df$tissue

# subset spots covered by tissue
ipf_obj = subset(ipf_obj, subset = tissue==1)
print(sprintf('spots covered by tissue: %d (%.2f%%)', ncol(ipf_obj), ncol(ipf_obj)/total_spots*100))
[1] "spots covered by tissue: 3532 (70.75%)"

3.2 Read estimated cell type proportions by all methods

NOTE: cell type deconvolution results from SpatialDWLS and CARD are fewer than all 3,532 spatial spots.

file_name = file.path(home.dir, 'IPF_all_results.rds')
all_res = readRDS(file_name)
print(sprintf('load data from %s', file_name))
[1] "load data from /home/hill103/Documents/Spatial/Figures/IPF/IPF_all_results.rds"
# check the order of spatial spots and cell types are consistent
all_spots = row.names(all_res[['SDePER']])
all_celltypes = colnames(all_res[['SDePER']])
all_methods = names(all_res)

Check the order of spots and cell types are consistent before performance evaluation.

for (method_name in all_methods) {
  if (method_name %notin% c('SpatialDWLS', 'CARD')) {
    stopifnot(all(row.names(all_res[[method_name]]) == all_spots))
  }
  stopifnot(all(colnames(all_res[[method_name]]) == all_celltypes))
}

Check whether negative values of estimated cell type proportions exist. Replace them as 0.

for (method_name in all_methods) {
  tmp_df = all_res[[method_name]]
  for (i in 1:nrow(tmp_df)) {
    for (j in 1:ncol(tmp_df)) {
      if (tmp_df[i, j] < 0) {
        print(sprintf('%s result: row %d (%s) column %d (%s) has negative value %g', method_name, i, row.names(tmp_df)[i], j, colnames(tmp_df)[j], tmp_df[i, j]))
        # replace them with 0
        all_res[[method_name]][i, j] = 0
      }
    }
  }
}

4 Draw figures

We use visualization functions SpatialFeaturePlot in Seurat to draw the heatmaps of gene expression and cell type proportions.

4.1 Figure 6B. Heatmap of 4 cell type-specific marker genes

MYH11 for SMC, FOXJ1 for Ciliated cells, AQP4 for ATI, SFTPA1 for ATII.

NOTE: we applied a truncation to the sequencing depth normalized gene expression such that any value >= 97th percentile is set to the value at the 97th percentile. Then we re-scale the expression values to range [0, 1] across all 3,532 spots.

if (save_file) {
  file_name = file.path(home.dir, 'Fig6B_IPF_4celltype_marker_heatmap.pdf')
  cairo_pdf(file_name, height=3, width=12, onefile=T)
  print(sprintf('figures saved in file %s', file_name))
}

g_list = list()

for (one_gene in unname(ipf_celltype_marker)) {
  stopifnot(one_gene %in% row.names(ipf_obj))
  tmp_gene_vec = ipf_obj@assays$spatial@data[one_gene, ]
  # in case that so many 0s cause 97th percentile is 0, if so use the smallest positive value as the threshold for truncation
  tmp_thresh = max(min(tmp_gene_vec[tmp_gene_vec>0]), quantile(tmp_gene_vec, 0.97))
  tmp_gene_vec[tmp_gene_vec>=tmp_thresh] = tmp_thresh
  # re-scale to range[0, 1]
  tmp_gene_vec = (tmp_gene_vec - min(tmp_gene_vec)) / (max(tmp_gene_vec) - min(tmp_gene_vec))
  # assign to meta data then plot
  ipf_obj[[one_gene]] = tmp_gene_vec
  g_list[[length(g_list)+1]] = SpatialFeaturePlot(ipf_obj, features = one_gene, image.alpha = 0) + 
    ggtitle(one_gene) + 
    theme(plot.title = element_text(size=14, hjust=0.5),
          legend.position = 'right',
          legend.title = element_text(face = 'bold')) +
    guides(fill = guide_colorbar(title = 'Exp'))
}

ggpubr::ggarrange(plotlist = g_list, nrow = 1, align = 'h', common.legend = T, legend = 'right')

4.2 Figure 6D. Barplot of weighted mean of expressions of 4 cell type-specific marker genes

We calculate the average expression of each marker gene across all spots weighted by predicted proportion of its corresponding cell type (MYH11 for SMC, FOXJ1 for Ciliated cells, AQP4 for ATI, SFTPA1 for ATII).

NOTE:

We use scaled gene expression already calculated in Figure 6B, then for each method, select the spots with prediction from that method, and further re-scale the expression values to range [0, 1] across selected spots again.

For one method and one cell type with its corresponding marker gene, we have predicted proportions \(w\) for \(n\) spots. The weighted mean is calculated by:

\[ Weighted Mean = \frac{\sum_{i=1}^{n} w_i \times x_i}{\sum_{i=1}^{n} w_i} \]

here \(x_i\) is the scaled gene expression for spot \(i\).

if (save_file) {
  file_name = file.path(home.dir, 'Fig6D_IPF_barplot_weighted_mean_exp.pdf')
  cairo_pdf(file_name, height=8, width=4, onefile=T)
  print(sprintf('figures saved in file %s', file_name))
}

avg_exp_df = data.frame(matrix(ncol=3, nrow=0))
colnames(avg_exp_df) = c('celltype', 'method', 'avg_exp')

for (method_name in names(all_res)) {
  tmp_select_spots = row.names(all_res[[method_name]])
  for (i in 1:length(ipf_celltype_marker)) {
    tmp_prop = all_res[[method_name]][, names(ipf_celltype_marker)[i], drop=T]
    tmp_exp = ipf_obj@meta.data[tmp_select_spots, ipf_celltype_marker[i], drop=T]
    # re-scale expression to range [0, 1] again
    tmp_exp = (tmp_exp - min(tmp_exp)) / (max(tmp_exp) - min(tmp_exp))
    # weighted average
    avg_exp = sum(tmp_prop * tmp_exp) / sum(tmp_prop)
    avg_exp_df[nrow(avg_exp_df)+1, ] = c(names(ipf_celltype_marker)[i], method_name, avg_exp)
  }
}

avg_exp_df[avg_exp_df$celltype=='SMC-Vascular', 'celltype'] = 'SMC'
avg_exp_df$celltype = factor(avg_exp_df$celltype, levels = c("SMC", "Ciliated", "ATI", "ATII"))
avg_exp_df$method = factor(avg_exp_df$method, levels = names(all_res))
avg_exp_df$avg_exp = as.numeric(avg_exp_df$avg_exp)

ggplot(avg_exp_df, aes(x=method, y=avg_exp, fill=method)) +
  geom_bar(stat="identity", color="black") +
  geom_text(aes(label=round(avg_exp, 3), y=ifelse(avg_exp<=0, avg_exp+0.05, avg_exp), color=ifelse(method %in% c('SPOTlight', 'DestVI'), "white", "black")), vjust=1.1, size=3.5, fontface="bold") +
  scale_fill_manual(values=method_color) +
  theme_classic() +
  theme(axis.text = element_text(colour="black", size=10),
        axis.text.x = element_text(angle=45, hjust=1),
        axis.title.x = element_blank(), axis.title.y = element_text(size=14),
        strip.text = element_text(size=12, colour="black"),
        legend.position = "none") +
  facet_wrap(celltype~., ncol = 1, scales = "free_y") +
  ylab("Weighted Mean of Expression") +
  scale_color_identity() # to make sure ggplot2 uses the set conditional colors

4.3 Figure 6C. Visualization of inferred cell type proportions in each spot of 4 methods

We only show 4 cell types (SMC, Ciliated cells, ATI and ATII cells) and 4 methods (SDePER, SpatialDWLS, DestVI and RCTD).

NOTE: we assign similar colors for cell type proportions falling in [0.5, 1].

if (save_file) {
  file_name = file.path(home.dir, 'Fig6C_IPF_4celltype_props_4methods.pdf')
  cairo_pdf(file_name, height=12, width=12, onefile=T)
  print(sprintf('figures saved in file %s', file_name))
}

myPalette = colorRampPalette(colors = rev(x = RColorBrewer::brewer.pal(n = 11, name = "Spectral")))

g_list = list()

for (method_name in c("SDePER", "SpatialDWLS", "DestVI", "RCTD")) {
  for (this_celltype in c("SMC-Vascular", "Ciliated", "ATI", "ATII")) {
    # assign to meta data then plot; Seurat will match the spot order automatically, and assign NA to spots without corresponding values
    if (this_celltype == 'SMC-Vascular') {
      ipf_obj[['SMC']] = all_res[[method_name]][, this_celltype, drop=F]
      plot_celltype = 'SMC'
    } else {
      ipf_obj[[this_celltype]] = all_res[[method_name]][, this_celltype, drop=F]
      plot_celltype = this_celltype
    }
    
    g_list[[length(g_list)+1]] = SpatialFeaturePlot(ipf_obj, features = plot_celltype, image.alpha = 0) +
      scale_fill_gradientn(colours = myPalette(100), values=c(0, 0.1, 0.2, 0.3, 0.4, 0.5, 1), breaks=c(0, 0.25, 0.5, 0.75, 1), limits=c(0, 1)) +
      theme(plot.title = element_text(size=14, hjust=0.5),
            axis.title.y = element_text(size=14),
            legend.position = 'right',
            legend.title = element_text(face = 'bold')) +
      guides(fill = guide_colorbar(title = 'Prop'))
    
    if (method_name == 'SDePER') {
      # add cell type name as figure title
      g_list[[length(g_list)]] = g_list[[length(g_list)]] + ggtitle(plot_celltype)
    }
    if (plot_celltype == 'SMC') {
      # add method name as y axis title
      g_list[[length(g_list)]] = g_list[[length(g_list)]] + ylab(method_name)
    } else {
      g_list[[length(g_list)]] = g_list[[length(g_list)]] + ylab('')
    }
  }
}

ggpubr::ggarrange(plotlist = g_list, ncol = 4, nrow = 4, align = 'hv', common.legend = T, legend = 'right')

4.4 Figure S12. Visualization of inferred cell type proportions in each spot of all methods

Compared with Figure 6C, we show the same 4 cell types (SMC, Ciliated cells, ATI and ATII cells) but all methods.

NOTE: we assign similar colors for cell type proportions falling in [0.5, 1].

if (save_file) {
  file_name = file.path(home.dir, 'FigS12_IPF_4celltype_props_allmethods.pdf')
  cairo_pdf(file_name, height=9, width=15, onefile=T)
  print(sprintf('figures saved in file %s', file_name))
}

myPalette = colorRampPalette(colors = rev(x = RColorBrewer::brewer.pal(n = 11, name = "Spectral")))

g_list = list()

for (this_celltype in c("SMC-Vascular", "Ciliated", "ATI", "ATII")) {
  for (method_name in names(all_res)) {
    # assign to meta data then plot; Seurat will match the spot order automatically, and assign NA to spots without corresponding values
    if (this_celltype == 'SMC-Vascular') {
      ipf_obj[['SMC']] = all_res[[method_name]][, this_celltype, drop=F]
      plot_celltype = 'SMC'
    } else {
      ipf_obj[[this_celltype]] = all_res[[method_name]][, this_celltype, drop=F]
      plot_celltype = this_celltype
    }
    
    g_list[[length(g_list)+1]] = SpatialFeaturePlot(ipf_obj, features = plot_celltype, image.alpha = 0) +
      scale_fill_gradientn(colours = myPalette(100), values=c(0, 0.1, 0.2, 0.3, 0.4, 0.5, 1), breaks=c(0, 0.25, 0.5, 0.75, 1), limits=c(0, 1)) +
      theme(plot.title = element_text(size=14, hjust=0.5),
            axis.title.y = element_text(size=14),
            legend.position = 'right',
            legend.title = element_text(face = 'bold')) +
      guides(fill = guide_colorbar(title = 'Prop'))
    
    if (method_name == 'SDePER') {
      # add cell type name as y axis title
      g_list[[length(g_list)]] = g_list[[length(g_list)]] + ylab(plot_celltype)
    } else {
      g_list[[length(g_list)]] = g_list[[length(g_list)]] + ylab('')
    }
    if (plot_celltype == 'SMC') {
      # add method name as figure title
      g_list[[length(g_list)]] = g_list[[length(g_list)]] + ggtitle(method_name)
    }
  }
}

ggpubr::ggarrange(plotlist = g_list, ncol = 7, nrow = 4, align = 'hv', common.legend = T, legend = 'right')

4.5 Figure 6E. Heatmap of pairwise correlation of estimated cell type proportions

NOTE: we use Spearman’s rank correlation coefficient, and set the color map to place greater emphasis (in terms of the number of distinct colors) on correlation coefficient range [-0.4, 0.4].

if (save_file) {
  file_name = file.path(home.dir, 'Fig6E_IPF_celltype_prop_cor_heatmap.pdf')
  cairo_pdf(file_name, height=7, width=18, onefile=T)
  print(sprintf('figures saved in file %s', file_name))
}

# first define a custom function to draw heatmap for one method
draw_corr_map = function(df, method_name, heatmap_color, show_axis_label, show_legend, show_row_group, cor_method = "spearman") {
  # Pre-defined order for rows and columns (cell types) in the heatmap
  celltype_order = c(
    "Goblet", "Basal", "Ciliated", "Fibroblast-Airway", "Mast",
    "AberrantBasaloid",
    "ATI", "ATII", "Fibroblast-Alveolar", "Pericyte-Alveolar", "VE_Capillary_A", "Macrophage_Alveolar", "Lymphatic", "VE_Venous",
    "SMC", "VE_Arterial", "Fibroblast-Adventitial",
    "B", "Macrophage", "NK", "T", "VE_Capillary_B", "cDC1", "cDC2", "cMonocyte", "ncMonocyte"
  )
  
  # Splitting factors for grouping of cell types in the heatmap
  split_groups = factor(
    c(rep('Airway', 5), ' ', rep('Alveoli', 8), rep('Vascular', 3), rep('  ', 9)),
    levels = c('Airway', ' ', 'Alveoli', 'Vascular', '  ')
  )
  
  # Compute the correlation matrix
  corr_mtx = cor(df, method = cor_method)
  
  # Re-order rows and columns based on the pre-defined order
  corr_mtx = corr_mtx[celltype_order, celltype_order]
  
  # Plot the heatmap
  ComplexHeatmap::Heatmap(
    corr_mtx,
    name = method_name,
    col = heatmap_color,
    cluster_columns = FALSE,
    cluster_rows = FALSE,
    row_split = split_groups,
    column_split = split_groups,
    column_title = method_name, # use it as the figure title
    row_title = show_row_group,
    show_row_names = show_axis_label, # show/hide row names
    show_column_names = show_axis_label, # show/hide column names
    show_heatmap_legend = show_legend,
    heatmap_legend_param = list(title = 'Corr'),
    # Draw rectangles around specific slices/groups in the heatmap
    layer_fun = function(j, i, x, y, width, height, fill, slice_r, slice_c) {
      if (slice_r == slice_c & slice_c %in% c(1, 3, 4)) {
        grid::grid.rect(gp = grid::gpar(lwd = 5, fill = "transparent"))
      }
    }
  )
}

# define colors for heatmap
col_palette = colorRampPalette(c("#67001F", "#B2182B", "#D6604D", "#F4A582", "#FDDBC7", "#FFFFFF", "#D1E5F0", "#92C5DE", "#4393C3", "#2166AC", "#053061"))
color_breaks = c(seq(-1,-0.4,length=50), seq(-0.4,0.4,length=200), seq(0.4,1,length=50))
heatmap_col = circlize::colorRamp2(color_breaks, rev(col_palette(length(color_breaks))))
  
g_list = list()

for (method_name in names(all_res)) {
  # replace cell type name SMC-Vascular to SMC
  tmp_df = all_res[[method_name]]
  colnames(tmp_df)[colnames(tmp_df)=='SMC-Vascular'] = 'SMC'
  if (method_name != 'SDePER') {
    g_list[[length(g_list)+1]] = draw_corr_map(tmp_df, method_name, heatmap_col, show_axis_label = F, show_legend = F, show_row_group = NULL, )
  } else {
    g_list[[length(g_list)+1]] = draw_corr_map(tmp_df, method_name, heatmap_col, show_axis_label = T, show_legend = F, show_row_group = character(0))
  }
}

g_legend = ComplexHeatmap::Legend(col_fun = heatmap_col, title = 'Corr', legend_height = unit(4, 'cm'), grid_width = unit(0.8, 'cm'), labels_gp = grid::gpar(fontsize = 16), title_gp = grid::gpar(fontsize = 18, font = 2))

# combine heatmaps using multipanelfigure
print(multi_panel_figure(width = c(80, 80, 80, 80, 80, 30), height = c(80, 80), panel_label_type    = 'none') %>%
  fill_panel(g_list[[1]], column = 1:2, row=1:2) %>%
  fill_panel(g_list[[2]], column = 3, row = 1) %>%
  fill_panel(g_list[[3]], column = 4, row = 1) %>%
  fill_panel(g_list[[4]], column = 5, row = 1) %>%
  fill_panel(g_list[[5]], column = 3, row = 2) %>%
  fill_panel(g_list[[6]], column = 4, row = 2) %>%
  fill_panel(g_list[[7]], column = 5, row = 2) %>%
  # use grid.grabExpr to capture the output of a graphical expression as a grob, then it can be supported by multipanelfigure
  fill_panel(grid::grid.grabExpr(grid::grid.draw(g_legend)), column = 6, row = 1:2)
)

LS0tCnRpdGxlOiAiR2VuZXJhdGUgZmlndXJlcyBpbiBSZWFsIERhdGEgQW5hbHlzaXM6IElQRiIKYXV0aG9yOiAiWXVucWluZyBMaXUgJiBOaW5nc2hhbiBMaSIKZGF0ZTogIjIwMjMvMDkvMjAiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDYKICAgIHRvY19mbG9hdDogeWVzCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdob2xkJywgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDUsIGRwaSA9IDMwMCkKCgojIHVzZSBnZ3B1YnIgcGFja2FnZSAoaHR0cDovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC9hcnRpY2xlcy8yNC1nZ3B1YnItcHVibGljYXRpb24tcmVhZHktcGxvdHMvODEtZ2dwbG90Mi1lYXN5LXdheS10by1taXgtbXVsdGlwbGUtZ3JhcGhzLW9uLXRoZS1zYW1lLXBhZ2UvKQojIG11bHRpcGFuZWxmaWd1cmUgc3VwcG9ydHMgYXJyYW5naW5nIG91dHB1dHMgZnJvbSBDb21wbGV4SGVhdG1hcApsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkobXVsdGlwYW5lbGZpZ3VyZSkKbGlicmFyeShTZXVyYXQpCgpgJW5vdGluJWAgPSBOZWdhdGUoYCVpbiVgKQoKc2V0LnNlZWQoMSkKCmhvbWUuZGlyID0gJy9ob21lL2hpbGwxMDMvRG9jdW1lbnRzL1NwYXRpYWwvRmlndXJlcy9JUEYnCnNhdmVfZmlsZSA9IEZBTFNFCgpteV9jb2xvciA9IGMoJyNlNjE5NGInLCAnIzNjYjQ0YicsICcjZmZlMTE5JywgJyM0MzYzZDgnLCAnI2Y1ODIzMScsICcjOTExZWI0JywgJyM0NmYwZjAnLCAnI2YwMzJlNicsICcjYmNmNjBjJywgJyNmYWJlYmUnLCAnIzAwODA4MCcsICcjZTZiZWZmJywgJyM5YTYzMjQnLCAnI2ZmZmFjOCcsICcjODAwMDAwJywgJyNhYWZmYzMnLCAnIzgwODAwMCcsICcjZmZkOGIxJywgJyMwMDAwNzUnLCAnIzgwODA4MCcsICcjZmZmZmZmJywgJyMwMDAwMDAnKQptZXRob2RfY29sb3IgPSBjKCJTRGVQRVIiPScjZTYxOTRiJywgIlNwYXRpYWxEV0xTIj0nIzNjYjQ0YicsICJjZWxsMmxvY2F0aW9uIj0nI2ZmZTExOScsICJTUE9UbGlnaHQiPScjNDM2M2Q4JywgIkNBUkQiPScjZjU4MjMxJywgIkRlc3RWSSI9JyM5MTFlYjQnLCAiUkNURCI9JyM0NmYwZjAnLCAiR0xSTSI9JyNmMDMyZTYnKQoKCmlwZl9jZWxsdHlwZV9tYXJrZXIgPSBjKCJTTUMtVmFzY3VsYXIiPSJNWUgxMSIsICJDaWxpYXRlZCI9IkZPWEoxIiwgIkFUSSI9IkFRUDQiLCAiQVRJSSI9IlNGVFBBMSIpCmBgYAoKCiMgU3VtbWFyeQoKVGhpcyBSIE5vdGVib29rIGdlbmVyYXRlcyBmaWd1cmVzIGZvciBpZGlvcGF0aGljIHB1bG1vbmFyeSBmaWJyb3NpcyAoKipJUEYqKikgbHVuZyBkYXRhc2V0IGluIFJlYWwgRGF0YSBBbmFseXNpcyBzZWN0aW9uIGluIG1hbnVzY3JpcHQuCgoxLiAqKklucHV0cyoqOgoKICAqIFtgSVBGX3NwYXRpYWxfZGF0YS5yZHNgXShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vUmVhbERhdGEvSVBGL0lQRl9zcGF0aWFsX2RhdGEucmRzKTogQSBgU2V1cmF0YCBvYmplY3Qgb2YgSVBGIGRhdGFzZXQsIGluY2x1ZGluZyByYXcgblVNSSBhbmQgcGh5c2ljYWwgbG9jYXRpb25zIG9mIHNwYXRpYWwgc3BvdHMsIGFuZCBhIHRodW1ibmFpbCBvZiB0aXNzdWUgaW1hZ2UuIEluIHRvdGFsIDQsOTkyIHNwYXRpYWwgc3BvdHMgYW5kIDYwLDY1MSBnZW5lcyBhcmUgaW5jbHVkZWQuCiAgKiBbYElQRl9hbGxfcmVzdWx0cy5yZHNgXShodHRwczovL2dpdGh1Yi5jb20vYXo3amgyL1NEZVBFUl9BbmFseXNpcy9ibG9iL21haW4vRmlndXJlcy9JUEYvSVBGX2FsbF9yZXN1bHRzLnJkcyk6IGNlbGwgdHlwZSBkZWNvbnZvbHV0aW9uIHJlc3VsdHMgb2YgYWxsIG1ldGhvZHMgaW4gSVBGIGRhdGEgYW5hbHlzaXMuCgoyLiAqKk91dHB1dHMqKjoKCiAgKiBGaWd1cmUgNkIuIEhlYXRtYXAgb2YgNCBjZWxsIHR5cGUtc3BlY2lmaWMgbWFya2VyIGdlbmVzCiAgKiBGaWd1cmUgNkMuIFZpc3VhbGl6YXRpb24gb2YgaW5mZXJyZWQgY2VsbCB0eXBlIHByb3BvcnRpb25zIGluIGVhY2ggc3BvdCBvZiA0IG1ldGhvZHMKICAqIEZpZ3VyZSA2RC4gQmFycGxvdCBvZiB3ZWlnaHRlZCBtZWFuIG9mIGV4cHJlc3Npb25zIG9mIDQgY2VsbCB0eXBlLXNwZWNpZmljIG1hcmtlciBnZW5lcwogICogRmlndXJlIDZFLiBIZWF0bWFwIG9mIHBhaXJ3aXNlIGNvcnJlbGF0aW9uIG9mIGVzdGltYXRlZCBjZWxsIHR5cGUgcHJvcG9ydGlvbnMKICAqIEZpZ3VyZSBTMTIuIFZpc3VhbGl6YXRpb24gb2YgaW5mZXJyZWQgY2VsbCB0eXBlIHByb3BvcnRpb25zIGluIGVhY2ggc3BvdCBvZiBhbGwgbWV0aG9kcwoKCiMgVmVyc2lvbgoKYGBge3J9CnZlcnNpb25bWyd2ZXJzaW9uLnN0cmluZyddXQpwcmludChzcHJpbnRmKCdQYWNrYWdlICVzIHZlcnNpb246ICVzJywgJ2dncGxvdDInLCBwYWNrYWdlVmVyc2lvbignZ2dwbG90MicpKSkKcHJpbnQoc3ByaW50ZignUGFja2FnZSAlcyB2ZXJzaW9uOiAlcycsICdDb21wbGV4SGVhdG1hcCcsIHBhY2thZ2VWZXJzaW9uKCdDb21wbGV4SGVhdG1hcCcpKSkKcHJpbnQoc3ByaW50ZignUGFja2FnZSAlcyB2ZXJzaW9uOiAlcycsICdnZ3B1YnInLCBwYWNrYWdlVmVyc2lvbignZ2dwdWJyJykpKQpwcmludChzcHJpbnRmKCdQYWNrYWdlICVzIHZlcnNpb246ICVzJywgJ211bHRpcGFuZWxmaWd1cmUnLCBwYWNrYWdlVmVyc2lvbignbXVsdGlwYW5lbGZpZ3VyZScpKSkKcHJpbnQoc3ByaW50ZignUGFja2FnZSAlcyB2ZXJzaW9uOiAlcycsICdTZXVyYXQnLCBwYWNrYWdlVmVyc2lvbignU2V1cmF0JykpKQpgYGAKCgojIFJlYWQgcmVsZXZhbnQgZmlsZXMKCiMjIFJlYWQgYFNldXJhdGAgb2JqZWN0IG9mIHNwYXRpYWwgZGF0YQoKYGBge3J9CmZpbGVfbmFtZSA9IGZpbGUucGF0aChob21lLmRpciwgJ0lQRl9zcGF0aWFsX2RhdGEucmRzJykKaXBmX29iaiA9IHJlYWRSRFMoZmlsZV9uYW1lKQpwcmludChzcHJpbnRmKCdsb2FkIGRhdGEgZnJvbSAlcycsIGZpbGVfbmFtZSkpCnByaW50KHNwcmludGYoJ2dlbmVzOiAlZCwgc3BvdHM6ICVkJywgbmNvbChpcGZfb2JqKSwgbnJvdyhpcGZfb2JqKSkpCmBgYAoKb25seSBmb2N1cyBvbiBzcG90cyBjb3ZlcmVkIGJ5IHRpc3N1ZQoKYGBge3J9CiMgYWRkIHRpc3N1ZSBpbmRpY2F0b3IgaW50byBtZXRhIGRhdGEKdG90YWxfc3BvdHMgPSBuY29sKGlwZl9vYmopCnRtcC5kZiA9IGlwZl9vYmpAaW1hZ2VzW1sxXV1AY29vcmRpbmF0ZXMKdG1wLmRmID0gdG1wLmRmW2NvbG5hbWVzKGlwZl9vYmopLCBdCnN0b3BpZm5vdChucm93KHRtcC5kZikgPT0gbmNvbChpcGZfb2JqKSkKaXBmX29ialtbJ3Rpc3N1ZSddXSA9IHRtcC5kZiR0aXNzdWUKCiMgc3Vic2V0IHNwb3RzIGNvdmVyZWQgYnkgdGlzc3VlCmlwZl9vYmogPSBzdWJzZXQoaXBmX29iaiwgc3Vic2V0ID0gdGlzc3VlPT0xKQpwcmludChzcHJpbnRmKCdzcG90cyBjb3ZlcmVkIGJ5IHRpc3N1ZTogJWQgKCUuMmYlJSknLCBuY29sKGlwZl9vYmopLCBuY29sKGlwZl9vYmopL3RvdGFsX3Nwb3RzKjEwMCkpCmBgYAoKCiMjIFJlYWQgZXN0aW1hdGVkIGNlbGwgdHlwZSBwcm9wb3J0aW9ucyBieSBhbGwgbWV0aG9kcwoKTk9URTogY2VsbCB0eXBlIGRlY29udm9sdXRpb24gcmVzdWx0cyBmcm9tICoqU3BhdGlhbERXTFMqKiBhbmQgKipDQVJEKiogYXJlIGZld2VyIHRoYW4gYWxsIDMsNTMyIHNwYXRpYWwgc3BvdHMuCgpgYGB7cn0KZmlsZV9uYW1lID0gZmlsZS5wYXRoKGhvbWUuZGlyLCAnSVBGX2FsbF9yZXN1bHRzLnJkcycpCmFsbF9yZXMgPSByZWFkUkRTKGZpbGVfbmFtZSkKcHJpbnQoc3ByaW50ZignbG9hZCBkYXRhIGZyb20gJXMnLCBmaWxlX25hbWUpKQoKIyBjaGVjayB0aGUgb3JkZXIgb2Ygc3BhdGlhbCBzcG90cyBhbmQgY2VsbCB0eXBlcyBhcmUgY29uc2lzdGVudAphbGxfc3BvdHMgPSByb3cubmFtZXMoYWxsX3Jlc1tbJ1NEZVBFUiddXSkKYWxsX2NlbGx0eXBlcyA9IGNvbG5hbWVzKGFsbF9yZXNbWydTRGVQRVInXV0pCmFsbF9tZXRob2RzID0gbmFtZXMoYWxsX3JlcykKYGBgCgoKQ2hlY2sgdGhlIG9yZGVyIG9mIHNwb3RzIGFuZCBjZWxsIHR5cGVzIGFyZSBjb25zaXN0ZW50IGJlZm9yZSBwZXJmb3JtYW5jZSBldmFsdWF0aW9uLgoKYGBge3J9CmZvciAobWV0aG9kX25hbWUgaW4gYWxsX21ldGhvZHMpIHsKICBpZiAobWV0aG9kX25hbWUgJW5vdGluJSBjKCdTcGF0aWFsRFdMUycsICdDQVJEJykpIHsKICAgIHN0b3BpZm5vdChhbGwocm93Lm5hbWVzKGFsbF9yZXNbW21ldGhvZF9uYW1lXV0pID09IGFsbF9zcG90cykpCiAgfQogIHN0b3BpZm5vdChhbGwoY29sbmFtZXMoYWxsX3Jlc1tbbWV0aG9kX25hbWVdXSkgPT0gYWxsX2NlbGx0eXBlcykpCn0KYGBgCgoKQ2hlY2sgd2hldGhlciBuZWdhdGl2ZSB2YWx1ZXMgb2YgZXN0aW1hdGVkIGNlbGwgdHlwZSBwcm9wb3J0aW9ucyBleGlzdC4gUmVwbGFjZSB0aGVtIGFzIDAuCgpgYGB7cn0KZm9yIChtZXRob2RfbmFtZSBpbiBhbGxfbWV0aG9kcykgewogIHRtcF9kZiA9IGFsbF9yZXNbW21ldGhvZF9uYW1lXV0KICBmb3IgKGkgaW4gMTpucm93KHRtcF9kZikpIHsKICAgIGZvciAoaiBpbiAxOm5jb2wodG1wX2RmKSkgewogICAgICBpZiAodG1wX2RmW2ksIGpdIDwgMCkgewogICAgICAgIHByaW50KHNwcmludGYoJyVzIHJlc3VsdDogcm93ICVkICglcykgY29sdW1uICVkICglcykgaGFzIG5lZ2F0aXZlIHZhbHVlICVnJywgbWV0aG9kX25hbWUsIGksIHJvdy5uYW1lcyh0bXBfZGYpW2ldLCBqLCBjb2xuYW1lcyh0bXBfZGYpW2pdLCB0bXBfZGZbaSwgal0pKQogICAgICAgICMgcmVwbGFjZSB0aGVtIHdpdGggMAogICAgICAgIGFsbF9yZXNbW21ldGhvZF9uYW1lXV1baSwgal0gPSAwCiAgICAgIH0KICAgIH0KICB9Cn0KYGBgCgoKIyBEcmF3IGZpZ3VyZXMKCldlIHVzZSB2aXN1YWxpemF0aW9uIGZ1bmN0aW9ucyBgU3BhdGlhbEZlYXR1cmVQbG90YCBpbiBgU2V1cmF0YCB0byBkcmF3IHRoZSBoZWF0bWFwcyBvZiBnZW5lIGV4cHJlc3Npb24gYW5kIGNlbGwgdHlwZSBwcm9wb3J0aW9ucy4KCiMjIEZpZ3VyZSA2Qi4gSGVhdG1hcCBvZiA0IGNlbGwgdHlwZS1zcGVjaWZpYyBtYXJrZXIgZ2VuZXMKCipNWUgxMSogZm9yIFNNQywgKkZPWEoxKiBmb3IgQ2lsaWF0ZWQgY2VsbHMsICpBUVA0KiBmb3IgQVRJLCAqU0ZUUEExKiBmb3IgQVRJSS4KCk5PVEU6IHdlIGFwcGxpZWQgYSAqKnRydW5jYXRpb24qKiB0byB0aGUgc2VxdWVuY2luZyBkZXB0aCBub3JtYWxpemVkIGdlbmUgZXhwcmVzc2lvbiBzdWNoIHRoYXQgYW55IHZhbHVlID49IDk3dGggcGVyY2VudGlsZSBpcyBzZXQgdG8gdGhlIHZhbHVlIGF0IHRoZSA5N3RoIHBlcmNlbnRpbGUuIFRoZW4gd2UgcmUtc2NhbGUgdGhlIGV4cHJlc3Npb24gdmFsdWVzIHRvIHJhbmdlIFswLCAxXSBhY3Jvc3MgYWxsIDMsNTMyIHNwb3RzLiAgCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTN9CmlmIChzYXZlX2ZpbGUpIHsKICBmaWxlX25hbWUgPSBmaWxlLnBhdGgoaG9tZS5kaXIsICdGaWc2Ql9JUEZfNGNlbGx0eXBlX21hcmtlcl9oZWF0bWFwLnBkZicpCiAgY2Fpcm9fcGRmKGZpbGVfbmFtZSwgaGVpZ2h0PTMsIHdpZHRoPTEyLCBvbmVmaWxlPVQpCiAgcHJpbnQoc3ByaW50ZignZmlndXJlcyBzYXZlZCBpbiBmaWxlICVzJywgZmlsZV9uYW1lKSkKfQoKZ19saXN0ID0gbGlzdCgpCgpmb3IgKG9uZV9nZW5lIGluIHVubmFtZShpcGZfY2VsbHR5cGVfbWFya2VyKSkgewogIHN0b3BpZm5vdChvbmVfZ2VuZSAlaW4lIHJvdy5uYW1lcyhpcGZfb2JqKSkKICB0bXBfZ2VuZV92ZWMgPSBpcGZfb2JqQGFzc2F5cyRzcGF0aWFsQGRhdGFbb25lX2dlbmUsIF0KICAjIGluIGNhc2UgdGhhdCBzbyBtYW55IDBzIGNhdXNlIDk3dGggcGVyY2VudGlsZSBpcyAwLCBpZiBzbyB1c2UgdGhlIHNtYWxsZXN0IHBvc2l0aXZlIHZhbHVlIGFzIHRoZSB0aHJlc2hvbGQgZm9yIHRydW5jYXRpb24KICB0bXBfdGhyZXNoID0gbWF4KG1pbih0bXBfZ2VuZV92ZWNbdG1wX2dlbmVfdmVjPjBdKSwgcXVhbnRpbGUodG1wX2dlbmVfdmVjLCAwLjk3KSkKICB0bXBfZ2VuZV92ZWNbdG1wX2dlbmVfdmVjPj10bXBfdGhyZXNoXSA9IHRtcF90aHJlc2gKICAjIHJlLXNjYWxlIHRvIHJhbmdlWzAsIDFdCiAgdG1wX2dlbmVfdmVjID0gKHRtcF9nZW5lX3ZlYyAtIG1pbih0bXBfZ2VuZV92ZWMpKSAvIChtYXgodG1wX2dlbmVfdmVjKSAtIG1pbih0bXBfZ2VuZV92ZWMpKQogICMgYXNzaWduIHRvIG1ldGEgZGF0YSB0aGVuIHBsb3QKICBpcGZfb2JqW1tvbmVfZ2VuZV1dID0gdG1wX2dlbmVfdmVjCiAgZ19saXN0W1tsZW5ndGgoZ19saXN0KSsxXV0gPSBTcGF0aWFsRmVhdHVyZVBsb3QoaXBmX29iaiwgZmVhdHVyZXMgPSBvbmVfZ2VuZSwgaW1hZ2UuYWxwaGEgPSAwKSArIAogICAgZ2d0aXRsZShvbmVfZ2VuZSkgKyAKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xNCwgaGp1c3Q9MC41KSwKICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICdyaWdodCcsCiAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICdib2xkJykpICsKICAgIGd1aWRlcyhmaWxsID0gZ3VpZGVfY29sb3JiYXIodGl0bGUgPSAnRXhwJykpCn0KCmdncHVicjo6Z2dhcnJhbmdlKHBsb3RsaXN0ID0gZ19saXN0LCBucm93ID0gMSwgYWxpZ24gPSAnaCcsIGNvbW1vbi5sZWdlbmQgPSBULCBsZWdlbmQgPSAncmlnaHQnKQpgYGAKCgojIyBGaWd1cmUgNkQuIEJhcnBsb3Qgb2Ygd2VpZ2h0ZWQgbWVhbiBvZiBleHByZXNzaW9ucyBvZiA0IGNlbGwgdHlwZS1zcGVjaWZpYyBtYXJrZXIgZ2VuZXMKCldlIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBleHByZXNzaW9uIG9mIGVhY2ggbWFya2VyIGdlbmUgYWNyb3NzIGFsbCBzcG90cyB3ZWlnaHRlZCBieSBwcmVkaWN0ZWQgcHJvcG9ydGlvbiBvZiBpdHMgY29ycmVzcG9uZGluZyBjZWxsIHR5cGUgKCpNWUgxMSogZm9yIFNNQywgKkZPWEoxKiBmb3IgQ2lsaWF0ZWQgY2VsbHMsICpBUVA0KiBmb3IgQVRJLCAqU0ZUUEExKiBmb3IgQVRJSSkuCgpOT1RFOgoKV2UgdXNlIHNjYWxlZCBnZW5lIGV4cHJlc3Npb24gYWxyZWFkeSBjYWxjdWxhdGVkIGluIEZpZ3VyZSA2QiwgdGhlbiBmb3IgZWFjaCBtZXRob2QsIHNlbGVjdCB0aGUgc3BvdHMgd2l0aCBwcmVkaWN0aW9uIGZyb20gdGhhdCBtZXRob2QsIGFuZCBmdXJ0aGVyIHJlLXNjYWxlIHRoZSBleHByZXNzaW9uIHZhbHVlcyB0byByYW5nZSBbMCwgMV0gYWNyb3NzICoqc2VsZWN0ZWQqKiBzcG90cyBhZ2Fpbi4KCkZvciBvbmUgbWV0aG9kIGFuZCBvbmUgY2VsbCB0eXBlIHdpdGggaXRzIGNvcnJlc3BvbmRpbmcgbWFya2VyIGdlbmUsIHdlIGhhdmUgcHJlZGljdGVkIHByb3BvcnRpb25zICR3JCBmb3IgJG4kIHNwb3RzLiBUaGUgd2VpZ2h0ZWQgbWVhbiBpcyBjYWxjdWxhdGVkIGJ5OiAKCiQkIFdlaWdodGVkIE1lYW4gPSBcZnJhY3tcc3VtX3tpPTF9XntufSB3X2kgXHRpbWVzIHhfaX17XHN1bV97aT0xfV57bn0gd19pfSAkJAoKaGVyZSAkeF9pJCBpcyB0aGUgc2NhbGVkIGdlbmUgZXhwcmVzc2lvbiBmb3Igc3BvdCAkaSQuCgpgYGB7ciwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9OH0KaWYgKHNhdmVfZmlsZSkgewogIGZpbGVfbmFtZSA9IGZpbGUucGF0aChob21lLmRpciwgJ0ZpZzZEX0lQRl9iYXJwbG90X3dlaWdodGVkX21lYW5fZXhwLnBkZicpCiAgY2Fpcm9fcGRmKGZpbGVfbmFtZSwgaGVpZ2h0PTgsIHdpZHRoPTQsIG9uZWZpbGU9VCkKICBwcmludChzcHJpbnRmKCdmaWd1cmVzIHNhdmVkIGluIGZpbGUgJXMnLCBmaWxlX25hbWUpKQp9CgphdmdfZXhwX2RmID0gZGF0YS5mcmFtZShtYXRyaXgobmNvbD0zLCBucm93PTApKQpjb2xuYW1lcyhhdmdfZXhwX2RmKSA9IGMoJ2NlbGx0eXBlJywgJ21ldGhvZCcsICdhdmdfZXhwJykKCmZvciAobWV0aG9kX25hbWUgaW4gbmFtZXMoYWxsX3JlcykpIHsKICB0bXBfc2VsZWN0X3Nwb3RzID0gcm93Lm5hbWVzKGFsbF9yZXNbW21ldGhvZF9uYW1lXV0pCiAgZm9yIChpIGluIDE6bGVuZ3RoKGlwZl9jZWxsdHlwZV9tYXJrZXIpKSB7CiAgICB0bXBfcHJvcCA9IGFsbF9yZXNbW21ldGhvZF9uYW1lXV1bLCBuYW1lcyhpcGZfY2VsbHR5cGVfbWFya2VyKVtpXSwgZHJvcD1UXQogICAgdG1wX2V4cCA9IGlwZl9vYmpAbWV0YS5kYXRhW3RtcF9zZWxlY3Rfc3BvdHMsIGlwZl9jZWxsdHlwZV9tYXJrZXJbaV0sIGRyb3A9VF0KICAgICMgcmUtc2NhbGUgZXhwcmVzc2lvbiB0byByYW5nZSBbMCwgMV0gYWdhaW4KICAgIHRtcF9leHAgPSAodG1wX2V4cCAtIG1pbih0bXBfZXhwKSkgLyAobWF4KHRtcF9leHApIC0gbWluKHRtcF9leHApKQogICAgIyB3ZWlnaHRlZCBhdmVyYWdlCiAgICBhdmdfZXhwID0gc3VtKHRtcF9wcm9wICogdG1wX2V4cCkgLyBzdW0odG1wX3Byb3ApCiAgICBhdmdfZXhwX2RmW25yb3coYXZnX2V4cF9kZikrMSwgXSA9IGMobmFtZXMoaXBmX2NlbGx0eXBlX21hcmtlcilbaV0sIG1ldGhvZF9uYW1lLCBhdmdfZXhwKQogIH0KfQoKYXZnX2V4cF9kZlthdmdfZXhwX2RmJGNlbGx0eXBlPT0nU01DLVZhc2N1bGFyJywgJ2NlbGx0eXBlJ10gPSAnU01DJwphdmdfZXhwX2RmJGNlbGx0eXBlID0gZmFjdG9yKGF2Z19leHBfZGYkY2VsbHR5cGUsIGxldmVscyA9IGMoIlNNQyIsICJDaWxpYXRlZCIsICJBVEkiLCAiQVRJSSIpKQphdmdfZXhwX2RmJG1ldGhvZCA9IGZhY3RvcihhdmdfZXhwX2RmJG1ldGhvZCwgbGV2ZWxzID0gbmFtZXMoYWxsX3JlcykpCmF2Z19leHBfZGYkYXZnX2V4cCA9IGFzLm51bWVyaWMoYXZnX2V4cF9kZiRhdmdfZXhwKQoKZ2dwbG90KGF2Z19leHBfZGYsIGFlcyh4PW1ldGhvZCwgeT1hdmdfZXhwLCBmaWxsPW1ldGhvZCkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG9yPSJibGFjayIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPXJvdW5kKGF2Z19leHAsIDMpLCB5PWlmZWxzZShhdmdfZXhwPD0wLCBhdmdfZXhwKzAuMDUsIGF2Z19leHApLCBjb2xvcj1pZmVsc2UobWV0aG9kICVpbiUgYygnU1BPVGxpZ2h0JywgJ0Rlc3RWSScpLCAid2hpdGUiLCAiYmxhY2siKSksIHZqdXN0PTEuMSwgc2l6ZT0zLjUsIGZvbnRmYWNlPSJib2xkIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1tZXRob2RfY29sb3IpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvdXI9ImJsYWNrIiwgc2l6ZT0xMCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMiwgY29sb3VyPSJibGFjayIpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGZhY2V0X3dyYXAoY2VsbHR5cGV+LiwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgeWxhYigiV2VpZ2h0ZWQgTWVhbiBvZiBFeHByZXNzaW9uIikgKwogIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkgIyB0byBtYWtlIHN1cmUgZ2dwbG90MiB1c2VzIHRoZSBzZXQgY29uZGl0aW9uYWwgY29sb3JzCmBgYAoKCiMjIEZpZ3VyZSA2Qy4gVmlzdWFsaXphdGlvbiBvZiBpbmZlcnJlZCBjZWxsIHR5cGUgcHJvcG9ydGlvbnMgaW4gZWFjaCBzcG90IG9mIDQgbWV0aG9kcwoKV2Ugb25seSBzaG93IDQgY2VsbCB0eXBlcyAoU01DLCBDaWxpYXRlZCBjZWxscywgQVRJIGFuZCBBVElJIGNlbGxzKSBhbmQgNCBtZXRob2RzIChTRGVQRVIsIFNwYXRpYWxEV0xTLCBEZXN0VkkgYW5kIFJDVEQpLgoKTk9URTogd2UgYXNzaWduIHNpbWlsYXIgY29sb3JzIGZvciBjZWxsIHR5cGUgcHJvcG9ydGlvbnMgZmFsbGluZyBpbiBbMC41LCAxXS4KCmBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9CmlmIChzYXZlX2ZpbGUpIHsKICBmaWxlX25hbWUgPSBmaWxlLnBhdGgoaG9tZS5kaXIsICdGaWc2Q19JUEZfNGNlbGx0eXBlX3Byb3BzXzRtZXRob2RzLnBkZicpCiAgY2Fpcm9fcGRmKGZpbGVfbmFtZSwgaGVpZ2h0PTEyLCB3aWR0aD0xMiwgb25lZmlsZT1UKQogIHByaW50KHNwcmludGYoJ2ZpZ3VyZXMgc2F2ZWQgaW4gZmlsZSAlcycsIGZpbGVfbmFtZSkpCn0KCm15UGFsZXR0ZSA9IGNvbG9yUmFtcFBhbGV0dGUoY29sb3JzID0gcmV2KHggPSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwobiA9IDExLCBuYW1lID0gIlNwZWN0cmFsIikpKQoKZ19saXN0ID0gbGlzdCgpCgpmb3IgKG1ldGhvZF9uYW1lIGluIGMoIlNEZVBFUiIsICJTcGF0aWFsRFdMUyIsICJEZXN0VkkiLCAiUkNURCIpKSB7CiAgZm9yICh0aGlzX2NlbGx0eXBlIGluIGMoIlNNQy1WYXNjdWxhciIsICJDaWxpYXRlZCIsICJBVEkiLCAiQVRJSSIpKSB7CiAgICAjIGFzc2lnbiB0byBtZXRhIGRhdGEgdGhlbiBwbG90OyBTZXVyYXQgd2lsbCBtYXRjaCB0aGUgc3BvdCBvcmRlciBhdXRvbWF0aWNhbGx5LCBhbmQgYXNzaWduIE5BIHRvIHNwb3RzIHdpdGhvdXQgY29ycmVzcG9uZGluZyB2YWx1ZXMKICAgIGlmICh0aGlzX2NlbGx0eXBlID09ICdTTUMtVmFzY3VsYXInKSB7CiAgICAgIGlwZl9vYmpbWydTTUMnXV0gPSBhbGxfcmVzW1ttZXRob2RfbmFtZV1dWywgdGhpc19jZWxsdHlwZSwgZHJvcD1GXQogICAgICBwbG90X2NlbGx0eXBlID0gJ1NNQycKICAgIH0gZWxzZSB7CiAgICAgIGlwZl9vYmpbW3RoaXNfY2VsbHR5cGVdXSA9IGFsbF9yZXNbW21ldGhvZF9uYW1lXV1bLCB0aGlzX2NlbGx0eXBlLCBkcm9wPUZdCiAgICAgIHBsb3RfY2VsbHR5cGUgPSB0aGlzX2NlbGx0eXBlCiAgICB9CiAgICAKICAgIGdfbGlzdFtbbGVuZ3RoKGdfbGlzdCkrMV1dID0gU3BhdGlhbEZlYXR1cmVQbG90KGlwZl9vYmosIGZlYXR1cmVzID0gcGxvdF9jZWxsdHlwZSwgaW1hZ2UuYWxwaGEgPSAwKSArCiAgICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBteVBhbGV0dGUoMTAwKSwgdmFsdWVzPWMoMCwgMC4xLCAwLjIsIDAuMywgMC40LCAwLjUsIDEpLCBicmVha3M9YygwLCAwLjI1LCAwLjUsIDAuNzUsIDEpLCBsaW1pdHM9YygwLCAxKSkgKwogICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTQsIGhqdXN0PTAuNSksCiAgICAgICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTE0KSwKICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gJ3JpZ2h0JywKICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAnYm9sZCcpKSArCiAgICAgIGd1aWRlcyhmaWxsID0gZ3VpZGVfY29sb3JiYXIodGl0bGUgPSAnUHJvcCcpKQogICAgCiAgICBpZiAobWV0aG9kX25hbWUgPT0gJ1NEZVBFUicpIHsKICAgICAgIyBhZGQgY2VsbCB0eXBlIG5hbWUgYXMgZmlndXJlIHRpdGxlCiAgICAgIGdfbGlzdFtbbGVuZ3RoKGdfbGlzdCldXSA9IGdfbGlzdFtbbGVuZ3RoKGdfbGlzdCldXSArIGdndGl0bGUocGxvdF9jZWxsdHlwZSkKICAgIH0KICAgIGlmIChwbG90X2NlbGx0eXBlID09ICdTTUMnKSB7CiAgICAgICMgYWRkIG1ldGhvZCBuYW1lIGFzIHkgYXhpcyB0aXRsZQogICAgICBnX2xpc3RbW2xlbmd0aChnX2xpc3QpXV0gPSBnX2xpc3RbW2xlbmd0aChnX2xpc3QpXV0gKyB5bGFiKG1ldGhvZF9uYW1lKQogICAgfSBlbHNlIHsKICAgICAgZ19saXN0W1tsZW5ndGgoZ19saXN0KV1dID0gZ19saXN0W1tsZW5ndGgoZ19saXN0KV1dICsgeWxhYignJykKICAgIH0KICB9Cn0KCmdncHVicjo6Z2dhcnJhbmdlKHBsb3RsaXN0ID0gZ19saXN0LCBuY29sID0gNCwgbnJvdyA9IDQsIGFsaWduID0gJ2h2JywgY29tbW9uLmxlZ2VuZCA9IFQsIGxlZ2VuZCA9ICdyaWdodCcpCmBgYAoKCiMjIEZpZ3VyZSBTMTIuIFZpc3VhbGl6YXRpb24gb2YgaW5mZXJyZWQgY2VsbCB0eXBlIHByb3BvcnRpb25zIGluIGVhY2ggc3BvdCBvZiBhbGwgbWV0aG9kcwoKQ29tcGFyZWQgd2l0aCBGaWd1cmUgNkMsIHdlIHNob3cgdGhlIHNhbWUgNCBjZWxsIHR5cGVzIChTTUMsIENpbGlhdGVkIGNlbGxzLCBBVEkgYW5kIEFUSUkgY2VsbHMpIGJ1dCBhbGwgbWV0aG9kcy4KCk5PVEU6IHdlIGFzc2lnbiBzaW1pbGFyIGNvbG9ycyBmb3IgY2VsbCB0eXBlIHByb3BvcnRpb25zIGZhbGxpbmcgaW4gWzAuNSwgMV0uCgpgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTl9CmlmIChzYXZlX2ZpbGUpIHsKICBmaWxlX25hbWUgPSBmaWxlLnBhdGgoaG9tZS5kaXIsICdGaWdTMTJfSVBGXzRjZWxsdHlwZV9wcm9wc19hbGxtZXRob2RzLnBkZicpCiAgY2Fpcm9fcGRmKGZpbGVfbmFtZSwgaGVpZ2h0PTksIHdpZHRoPTE1LCBvbmVmaWxlPVQpCiAgcHJpbnQoc3ByaW50ZignZmlndXJlcyBzYXZlZCBpbiBmaWxlICVzJywgZmlsZV9uYW1lKSkKfQoKbXlQYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShjb2xvcnMgPSByZXYoeCA9IFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbChuID0gMTEsIG5hbWUgPSAiU3BlY3RyYWwiKSkpCgpnX2xpc3QgPSBsaXN0KCkKCmZvciAodGhpc19jZWxsdHlwZSBpbiBjKCJTTUMtVmFzY3VsYXIiLCAiQ2lsaWF0ZWQiLCAiQVRJIiwgIkFUSUkiKSkgewogIGZvciAobWV0aG9kX25hbWUgaW4gbmFtZXMoYWxsX3JlcykpIHsKICAgICMgYXNzaWduIHRvIG1ldGEgZGF0YSB0aGVuIHBsb3Q7IFNldXJhdCB3aWxsIG1hdGNoIHRoZSBzcG90IG9yZGVyIGF1dG9tYXRpY2FsbHksIGFuZCBhc3NpZ24gTkEgdG8gc3BvdHMgd2l0aG91dCBjb3JyZXNwb25kaW5nIHZhbHVlcwogICAgaWYgKHRoaXNfY2VsbHR5cGUgPT0gJ1NNQy1WYXNjdWxhcicpIHsKICAgICAgaXBmX29ialtbJ1NNQyddXSA9IGFsbF9yZXNbW21ldGhvZF9uYW1lXV1bLCB0aGlzX2NlbGx0eXBlLCBkcm9wPUZdCiAgICAgIHBsb3RfY2VsbHR5cGUgPSAnU01DJwogICAgfSBlbHNlIHsKICAgICAgaXBmX29ialtbdGhpc19jZWxsdHlwZV1dID0gYWxsX3Jlc1tbbWV0aG9kX25hbWVdXVssIHRoaXNfY2VsbHR5cGUsIGRyb3A9Rl0KICAgICAgcGxvdF9jZWxsdHlwZSA9IHRoaXNfY2VsbHR5cGUKICAgIH0KICAgIAogICAgZ19saXN0W1tsZW5ndGgoZ19saXN0KSsxXV0gPSBTcGF0aWFsRmVhdHVyZVBsb3QoaXBmX29iaiwgZmVhdHVyZXMgPSBwbG90X2NlbGx0eXBlLCBpbWFnZS5hbHBoYSA9IDApICsKICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IG15UGFsZXR0ZSgxMDApLCB2YWx1ZXM9YygwLCAwLjEsIDAuMiwgMC4zLCAwLjQsIDAuNSwgMSksIGJyZWFrcz1jKDAsIDAuMjUsIDAuNSwgMC43NSwgMSksIGxpbWl0cz1jKDAsIDEpKSArCiAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xNCwgaGp1c3Q9MC41KSwKICAgICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTQpLAogICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAncmlnaHQnLAogICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICdib2xkJykpICsKICAgICAgZ3VpZGVzKGZpbGwgPSBndWlkZV9jb2xvcmJhcih0aXRsZSA9ICdQcm9wJykpCiAgICAKICAgIGlmIChtZXRob2RfbmFtZSA9PSAnU0RlUEVSJykgewogICAgICAjIGFkZCBjZWxsIHR5cGUgbmFtZSBhcyB5IGF4aXMgdGl0bGUKICAgICAgZ19saXN0W1tsZW5ndGgoZ19saXN0KV1dID0gZ19saXN0W1tsZW5ndGgoZ19saXN0KV1dICsgeWxhYihwbG90X2NlbGx0eXBlKQogICAgfSBlbHNlIHsKICAgICAgZ19saXN0W1tsZW5ndGgoZ19saXN0KV1dID0gZ19saXN0W1tsZW5ndGgoZ19saXN0KV1dICsgeWxhYignJykKICAgIH0KICAgIGlmIChwbG90X2NlbGx0eXBlID09ICdTTUMnKSB7CiAgICAgICMgYWRkIG1ldGhvZCBuYW1lIGFzIGZpZ3VyZSB0aXRsZQogICAgICBnX2xpc3RbW2xlbmd0aChnX2xpc3QpXV0gPSBnX2xpc3RbW2xlbmd0aChnX2xpc3QpXV0gKyBnZ3RpdGxlKG1ldGhvZF9uYW1lKQogICAgfQogIH0KfQoKZ2dwdWJyOjpnZ2FycmFuZ2UocGxvdGxpc3QgPSBnX2xpc3QsIG5jb2wgPSA3LCBucm93ID0gNCwgYWxpZ24gPSAnaHYnLCBjb21tb24ubGVnZW5kID0gVCwgbGVnZW5kID0gJ3JpZ2h0JykKYGBgCgoKIyMgRmlndXJlIDZFLiBIZWF0bWFwIG9mIHBhaXJ3aXNlIGNvcnJlbGF0aW9uIG9mIGVzdGltYXRlZCBjZWxsIHR5cGUgcHJvcG9ydGlvbnMKCk5PVEU6IHdlIHVzZSAqKlNwZWFybWFuJ3MgcmFuayBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCoqLCBhbmQgc2V0IHRoZSBjb2xvciBtYXAgdG8gcGxhY2UgZ3JlYXRlciBlbXBoYXNpcyAoaW4gdGVybXMgb2YgdGhlIG51bWJlciBvZiBkaXN0aW5jdCBjb2xvcnMpIG9uIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IHJhbmdlIFstMC40LCAwLjRdLgoKYGBge3IsIGZpZy53aWR0aD0xOCwgZmlnLmhlaWdodD03fQppZiAoc2F2ZV9maWxlKSB7CiAgZmlsZV9uYW1lID0gZmlsZS5wYXRoKGhvbWUuZGlyLCAnRmlnNkVfSVBGX2NlbGx0eXBlX3Byb3BfY29yX2hlYXRtYXAucGRmJykKICBjYWlyb19wZGYoZmlsZV9uYW1lLCBoZWlnaHQ9Nywgd2lkdGg9MTgsIG9uZWZpbGU9VCkKICBwcmludChzcHJpbnRmKCdmaWd1cmVzIHNhdmVkIGluIGZpbGUgJXMnLCBmaWxlX25hbWUpKQp9CgojIGZpcnN0IGRlZmluZSBhIGN1c3RvbSBmdW5jdGlvbiB0byBkcmF3IGhlYXRtYXAgZm9yIG9uZSBtZXRob2QKZHJhd19jb3JyX21hcCA9IGZ1bmN0aW9uKGRmLCBtZXRob2RfbmFtZSwgaGVhdG1hcF9jb2xvciwgc2hvd19heGlzX2xhYmVsLCBzaG93X2xlZ2VuZCwgc2hvd19yb3dfZ3JvdXAsIGNvcl9tZXRob2QgPSAic3BlYXJtYW4iKSB7CiAgIyBQcmUtZGVmaW5lZCBvcmRlciBmb3Igcm93cyBhbmQgY29sdW1ucyAoY2VsbCB0eXBlcykgaW4gdGhlIGhlYXRtYXAKICBjZWxsdHlwZV9vcmRlciA9IGMoCiAgICAiR29ibGV0IiwgIkJhc2FsIiwgIkNpbGlhdGVkIiwgIkZpYnJvYmxhc3QtQWlyd2F5IiwgIk1hc3QiLAogICAgIkFiZXJyYW50QmFzYWxvaWQiLAogICAgIkFUSSIsICJBVElJIiwgIkZpYnJvYmxhc3QtQWx2ZW9sYXIiLCAiUGVyaWN5dGUtQWx2ZW9sYXIiLCAiVkVfQ2FwaWxsYXJ5X0EiLCAiTWFjcm9waGFnZV9BbHZlb2xhciIsICJMeW1waGF0aWMiLCAiVkVfVmVub3VzIiwKICAgICJTTUMiLCAiVkVfQXJ0ZXJpYWwiLCAiRmlicm9ibGFzdC1BZHZlbnRpdGlhbCIsCiAgICAiQiIsICJNYWNyb3BoYWdlIiwgIk5LIiwgIlQiLCAiVkVfQ2FwaWxsYXJ5X0IiLCAiY0RDMSIsICJjREMyIiwgImNNb25vY3l0ZSIsICJuY01vbm9jeXRlIgogICkKICAKICAjIFNwbGl0dGluZyBmYWN0b3JzIGZvciBncm91cGluZyBvZiBjZWxsIHR5cGVzIGluIHRoZSBoZWF0bWFwCiAgc3BsaXRfZ3JvdXBzID0gZmFjdG9yKAogICAgYyhyZXAoJ0FpcndheScsIDUpLCAnICcsIHJlcCgnQWx2ZW9saScsIDgpLCByZXAoJ1Zhc2N1bGFyJywgMyksIHJlcCgnICAnLCA5KSksCiAgICBsZXZlbHMgPSBjKCdBaXJ3YXknLCAnICcsICdBbHZlb2xpJywgJ1Zhc2N1bGFyJywgJyAgJykKICApCiAgCiAgIyBDb21wdXRlIHRoZSBjb3JyZWxhdGlvbiBtYXRyaXgKICBjb3JyX210eCA9IGNvcihkZiwgbWV0aG9kID0gY29yX21ldGhvZCkKICAKICAjIFJlLW9yZGVyIHJvd3MgYW5kIGNvbHVtbnMgYmFzZWQgb24gdGhlIHByZS1kZWZpbmVkIG9yZGVyCiAgY29ycl9tdHggPSBjb3JyX210eFtjZWxsdHlwZV9vcmRlciwgY2VsbHR5cGVfb3JkZXJdCiAgCiAgIyBQbG90IHRoZSBoZWF0bWFwCiAgQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAoCiAgICBjb3JyX210eCwKICAgIG5hbWUgPSBtZXRob2RfbmFtZSwKICAgIGNvbCA9IGhlYXRtYXBfY29sb3IsCiAgICBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSwKICAgIGNsdXN0ZXJfcm93cyA9IEZBTFNFLAogICAgcm93X3NwbGl0ID0gc3BsaXRfZ3JvdXBzLAogICAgY29sdW1uX3NwbGl0ID0gc3BsaXRfZ3JvdXBzLAogICAgY29sdW1uX3RpdGxlID0gbWV0aG9kX25hbWUsICMgdXNlIGl0IGFzIHRoZSBmaWd1cmUgdGl0bGUKICAgIHJvd190aXRsZSA9IHNob3dfcm93X2dyb3VwLAogICAgc2hvd19yb3dfbmFtZXMgPSBzaG93X2F4aXNfbGFiZWwsICMgc2hvdy9oaWRlIHJvdyBuYW1lcwogICAgc2hvd19jb2x1bW5fbmFtZXMgPSBzaG93X2F4aXNfbGFiZWwsICMgc2hvdy9oaWRlIGNvbHVtbiBuYW1lcwogICAgc2hvd19oZWF0bWFwX2xlZ2VuZCA9IHNob3dfbGVnZW5kLAogICAgaGVhdG1hcF9sZWdlbmRfcGFyYW0gPSBsaXN0KHRpdGxlID0gJ0NvcnInKSwKICAgICMgRHJhdyByZWN0YW5nbGVzIGFyb3VuZCBzcGVjaWZpYyBzbGljZXMvZ3JvdXBzIGluIHRoZSBoZWF0bWFwCiAgICBsYXllcl9mdW4gPSBmdW5jdGlvbihqLCBpLCB4LCB5LCB3aWR0aCwgaGVpZ2h0LCBmaWxsLCBzbGljZV9yLCBzbGljZV9jKSB7CiAgICAgIGlmIChzbGljZV9yID09IHNsaWNlX2MgJiBzbGljZV9jICVpbiUgYygxLCAzLCA0KSkgewogICAgICAgIGdyaWQ6OmdyaWQucmVjdChncCA9IGdyaWQ6OmdwYXIobHdkID0gNSwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKQogICAgICB9CiAgICB9CiAgKQp9CgojIGRlZmluZSBjb2xvcnMgZm9yIGhlYXRtYXAKY29sX3BhbGV0dGUgPSBjb2xvclJhbXBQYWxldHRlKGMoIiM2NzAwMUYiLCAiI0IyMTgyQiIsICIjRDY2MDREIiwgIiNGNEE1ODIiLCAiI0ZEREJDNyIsICIjRkZGRkZGIiwgIiNEMUU1RjAiLCAiIzkyQzVERSIsICIjNDM5M0MzIiwgIiMyMTY2QUMiLCAiIzA1MzA2MSIpKQpjb2xvcl9icmVha3MgPSBjKHNlcSgtMSwtMC40LGxlbmd0aD01MCksIHNlcSgtMC40LDAuNCxsZW5ndGg9MjAwKSwgc2VxKDAuNCwxLGxlbmd0aD01MCkpCmhlYXRtYXBfY29sID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoY29sb3JfYnJlYWtzLCByZXYoY29sX3BhbGV0dGUobGVuZ3RoKGNvbG9yX2JyZWFrcykpKSkKICAKZ19saXN0ID0gbGlzdCgpCgpmb3IgKG1ldGhvZF9uYW1lIGluIG5hbWVzKGFsbF9yZXMpKSB7CiAgIyByZXBsYWNlIGNlbGwgdHlwZSBuYW1lIFNNQy1WYXNjdWxhciB0byBTTUMKICB0bXBfZGYgPSBhbGxfcmVzW1ttZXRob2RfbmFtZV1dCiAgY29sbmFtZXModG1wX2RmKVtjb2xuYW1lcyh0bXBfZGYpPT0nU01DLVZhc2N1bGFyJ10gPSAnU01DJwogIGlmIChtZXRob2RfbmFtZSAhPSAnU0RlUEVSJykgewogICAgZ19saXN0W1tsZW5ndGgoZ19saXN0KSsxXV0gPSBkcmF3X2NvcnJfbWFwKHRtcF9kZiwgbWV0aG9kX25hbWUsIGhlYXRtYXBfY29sLCBzaG93X2F4aXNfbGFiZWwgPSBGLCBzaG93X2xlZ2VuZCA9IEYsIHNob3dfcm93X2dyb3VwID0gTlVMTCwgKQogIH0gZWxzZSB7CiAgICBnX2xpc3RbW2xlbmd0aChnX2xpc3QpKzFdXSA9IGRyYXdfY29ycl9tYXAodG1wX2RmLCBtZXRob2RfbmFtZSwgaGVhdG1hcF9jb2wsIHNob3dfYXhpc19sYWJlbCA9IFQsIHNob3dfbGVnZW5kID0gRiwgc2hvd19yb3dfZ3JvdXAgPSBjaGFyYWN0ZXIoMCkpCiAgfQp9CgpnX2xlZ2VuZCA9IENvbXBsZXhIZWF0bWFwOjpMZWdlbmQoY29sX2Z1biA9IGhlYXRtYXBfY29sLCB0aXRsZSA9ICdDb3JyJywgbGVnZW5kX2hlaWdodCA9IHVuaXQoNCwgJ2NtJyksIGdyaWRfd2lkdGggPSB1bml0KDAuOCwgJ2NtJyksIGxhYmVsc19ncCA9IGdyaWQ6OmdwYXIoZm9udHNpemUgPSAxNiksIHRpdGxlX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDE4LCBmb250ID0gMikpCgojIGNvbWJpbmUgaGVhdG1hcHMgdXNpbmcgbXVsdGlwYW5lbGZpZ3VyZQpwcmludChtdWx0aV9wYW5lbF9maWd1cmUod2lkdGggPSBjKDgwLCA4MCwgODAsIDgwLCA4MCwgMzApLCBoZWlnaHQgPSBjKDgwLCA4MCksIHBhbmVsX2xhYmVsX3R5cGUJPSAnbm9uZScpICU+JQogIGZpbGxfcGFuZWwoZ19saXN0W1sxXV0sIGNvbHVtbiA9IDE6Miwgcm93PTE6MikgJT4lCiAgZmlsbF9wYW5lbChnX2xpc3RbWzJdXSwgY29sdW1uID0gMywgcm93ID0gMSkgJT4lCiAgZmlsbF9wYW5lbChnX2xpc3RbWzNdXSwgY29sdW1uID0gNCwgcm93ID0gMSkgJT4lCiAgZmlsbF9wYW5lbChnX2xpc3RbWzRdXSwgY29sdW1uID0gNSwgcm93ID0gMSkgJT4lCiAgZmlsbF9wYW5lbChnX2xpc3RbWzVdXSwgY29sdW1uID0gMywgcm93ID0gMikgJT4lCiAgZmlsbF9wYW5lbChnX2xpc3RbWzZdXSwgY29sdW1uID0gNCwgcm93ID0gMikgJT4lCiAgZmlsbF9wYW5lbChnX2xpc3RbWzddXSwgY29sdW1uID0gNSwgcm93ID0gMikgJT4lCiAgIyB1c2UgZ3JpZC5ncmFiRXhwciB0byBjYXB0dXJlIHRoZSBvdXRwdXQgb2YgYSBncmFwaGljYWwgZXhwcmVzc2lvbiBhcyBhIGdyb2IsIHRoZW4gaXQgY2FuIGJlIHN1cHBvcnRlZCBieSBtdWx0aXBhbmVsZmlndXJlCiAgZmlsbF9wYW5lbChncmlkOjpncmlkLmdyYWJFeHByKGdyaWQ6OmdyaWQuZHJhdyhnX2xlZ2VuZCkpLCBjb2x1bW4gPSA2LCByb3cgPSAxOjIpCikKYGBgCgoK